 #include "audio.h"

 // *************************************** LECTEUR

 // Fonction callback appelee par le thread audio
 void mix           (Lecture * l, Uint8 *stream, int len);
 void mix_audio     (Source * s , Uint8 *stream, int len);
 void callback_audio(void *udata, Uint8 *stream, int len) {
   Lecteur * lecteur = (Lecteur *)udata;
   int i;

   SDL_LockMutex(lecteur->mutex); // on bloque la libération des données

   if(lecteur->statut!=STATUT_ERREUR)
 	for(i=0; i<lecteur->nbs; i++) {
 		Source * s = (Source *)((lecteur->sources)[i]);
 		mix_audio(s, stream, len);
 	}
   SDL_UnlockMutex(lecteur->mutex); // On dévérouille
 }

 // Initialisation : ouvre le périphérique audio conformément aux paramètres
 void initLecteur(Lecteur * l, int _frequence, Uint16 _format, Uint8 _nbcanaux, Uint16 _taille_audiobuffer) {
   SDL_AudioSpec sortie;
   SDL_AudioSpec obtenu;

   l->mutex        = SDL_CreateMutex();
   l->nbs          = 0;
   sortie.freq     = _frequence;
   sortie.format   = _format;
   sortie.channels = _nbcanaux;
   sortie.samples  = _taille_audiobuffer;
   sortie.callback = callback_audio;
   sortie.userdata = l; // l'instance de lecteur est la donnée utilisateur

   if (SDL_OpenAudio(&sortie, &obtenu)) l->statut = STATUT_ERREUR;
   else {
 	// Il n'y a pas eu d'erreur => on mémorise les paramètres obtenus :
 	l->statut    = STATUT_STOP;
 	l->frequence = obtenu.freq;
 	l->format    = obtenu.format;
 	l->nbcanaux  = obtenu.channels;
 	l->silence   = obtenu.silence;
 	switch(l->format) {
 	case AUDIO_S8 : l->taille_bloc = l->nbcanaux; break;
 	case AUDIO_U8 : l->taille_bloc = l->nbcanaux; break;
 	default       : l->taille_bloc = 2*l->nbcanaux;
 	}
             	SDL_PauseAudio(0);
   }
 }
    
 // Fermeture du péripherique audio
 void closeLecteur(Lecteur * l) {
   if (l->statut!=STATUT_ERREUR) {
 	while (l->nbs>0) closeSource((Source *)((l->sources)[0]));
 	SDL_CloseAudio();          // fin du thread audio
   }
 }

 // Activation du thread audio (la restitution démarre)
 void restitution(Lecteur * l) {
   if (l->statut!=STATUT_ERREUR) {
 	l->statut = STATUT_PLAY;
   }
 }

 // Affichage des caractéristiques du lecteur :
 void printLecteur(Lecteur * l) {
   char * ch_format;
   switch(l->format) {
   case AUDIO_S8     : ch_format = "AUDIO_S8"; break;
   case AUDIO_S16    : ch_format = "AUDIO_S16"; break;
   case AUDIO_U8     : ch_format = "AUDIO_U8"; break;
   case AUDIO_U16    : ch_format = "AUDIO_U16"; break;
   case AUDIO_S16MSB : ch_format = "AUDIO_S16MSB"; break;
   case AUDIO_U16MSB : ch_format = "AUDIO_U16MSB"; break;
   default           : ch_format = "?";
   }
   printf("====== LECTEUR ======\n");
   if (l->statut == STATUT_ERREUR)
 	printf("*** ERREUR : %s\n", SDL_GetError());
   else {
 	printf("Etat      : %s\n", (SDL_GetAudioStatus()==SDL_AUDIO_PLAYING ? "OUVERT" : "FERME"));
 	printf("Frequence : %i Hz\n", l->frequence);
 	printf("Format    : %s\n", ch_format);
 	printf("Canaux    : %i\n", (int)l->nbcanaux);
 	printf("Sources   : %i\n", l->nbs);
   }
 }

 // *************************************** SOURCE
 // MIXAGE SOURCE : appelé par callback_audio
 void mix_audio(Source * s, Uint8 *stream, int len) {
   int i=0;
   if(s->erreur == FAUX) {
 	while(i < (s->nbl)) {
 	  Lecture * lecture = (Lecture *)((s->lectures)[i]);
 	  if (lecture->statut == STATUT_PLAY) mix(lecture, stream, len);
 	  // on enleve les lectures finies :
 	  if (lecture->liberation == LIBERE_MAINTENANT || (lecture->liberation == LIBERE_ALAFIN && lecture->statut == STATUT_STOP)) {
 		(s->lectures)[i]=(s->lectures)[s->nbl-1];
 		free(lecture);
 		s->nbl--;
 	  }
 	  else i++;
 	}
   }
 }
 // Initialisation : charge et converti le fichier
 void initSource(Source * s, Lecteur * _lecteur, char * _fichier) {
   SDL_AudioSpec wav;
   Uint8 *       wav_buf;
   Uint32        wav_len;
   SDL_AudioCVT  wav_cvt;

   s->lecteur = _lecteur;
   s->fichier = _fichier;
   s->buffer  = NULL;
   s->nbl     = 0;

   // On vérifie d'abord que le lecteur ne pose pas de problème :
   s->erreur = (s->lecteur->statut == STATUT_ERREUR ? VRAI : FAUX);
   // S'il ny a pas d'erreur, on charge le fichier :
   if (s->erreur == FAUX) {
 	s->erreur = ( SDL_LoadWAV(s->fichier, &wav, &wav_buf, &wav_len) == NULL ? VRAI : FAUX);
    // S'il ny a pas eu d'erreur, on prepare la conversion :
 	if (s->erreur == FAUX) {
 	  s->erreur = ( SDL_BuildAudioCVT
 		 (&wav_cvt,
 		  wav.format,      wav.channels,      wav.freq,
 		  s->lecteur->format, s->lecteur->nbcanaux, s->lecteur->frequence)
 		 < 0 ? VRAI : FAUX);
 	  // s'il n'y a pas eu d'erreur, on fait la conversion :
 	  if (s->erreur == FAUX) {
 	s->buffer = (Uint8*)malloc(wav_len * wav_cvt.len_mult);
 	s->erreur = (s->buffer == NULL ? VRAI : FAUX);
 	// s'il n'y a pas eu de problème d'allocation, on continue :
 	if (s->erreur == FAUX) {
 	  memcpy(s->buffer, wav_buf, wav_len);
 	  wav_cvt.len = wav_len;
 	  wav_cvt.buf = s->buffer;  
 	  s->erreur = (SDL_ConvertAudio(&wav_cvt) ? VRAI : FAUX);
 	  // s'il n'y a pas eu d'erreur, on calcule les attributs derivés,
 	  // et on inscrit la source dans le lecteur :
 	  if (s->erreur == FAUX) {
 		s->taille = wav_cvt.len_cvt;
 		s->duree = (float)(s->taille/s->lecteur->taille_bloc)/s->lecteur->frequence;
 		s->erreur = (s->lecteur->nbs == NBMAX_SOURCES ? VRAI : FAUX);
					if (s->erreur == FAUX) {
 		SDL_LockMutex(s->lecteur->mutex);   // lectures peut-être en cours ?
 			s->lecteur->sources[s->lecteur->nbs]=s;
 		s->lecteur->nbs++;
               SDL_UnlockMutex(s->lecteur->mutex); // On dévérouille
 		}
 	  }
 	}
 	  }
 	  // on libere ce qui a ete alloue par SDL_LoadWAV :
 	  SDL_FreeWAV(wav_buf);
 	}
   }
 }

 // Libération de la source
 void closeSource(Source * s) {
   int i;
   SDL_LockMutex(s->lecteur->mutex); // des lectures sont peut-être en cours
   if (s->buffer!=NULL) free(s->buffer);
   if (s->erreur == FAUX) {
 	// On désinscrit la source dans le lecteur :
 	for (i=0; i< (s->lecteur->nbs); i++) {
 	  Source * source = (Source *)((s->lecteur->sources)[i]);
 	  if (source == s) {
 	(s->lecteur->sources)[i]=(s->lecteur->sources)[s->lecteur->nbs-1];
 	s->lecteur->nbs--;
 	break;
 	  }
 	}
 	// On supprime les lectures :
 	for(i=0; i< (s->nbl); i++) {
 	  Lecture * lecture = (Lecture *)((s->lectures)[i]);
 	  free(lecture);
 	}
 	(s->nbl)=0;
   }
   SDL_UnlockMutex(s->lecteur->mutex); // On dévérouille
 }
 // Affichage des caractéristiques de la source :
 void printSource(Source * s) {
   printf("SOURCE %s", s->fichier);
   if (s->erreur == VRAI) printf(" *** ERREUR ");
   else {
 	int minutesT =(int)(s->duree / 60);
 	float secondesT = s->duree - (minutesT*60);
 	printf(" (duree : %02i'%05.2f) lectures : %i\n",
 		minutesT, secondesT, s->nbl);
   }
 }

 // *************************************** LECTURE

 void mix(Lecture * l, Uint8 *stream, int len) {
   Uint8 tempG[len]; // buffer contenant la piste de gauche
   Uint8 tempD[len]; // buffer contenant la piste de droite
   int i=0;          // indice de remplissage de tempG et tempD
   int j;            // indice d'ecriture d'un bloc d'echantillon

   while (i<len && l->statut==STATUT_PLAY) {
 	int i_buffer = (int)l->position*l->taille_bloc;
 	// remplissage des buffers
 	for (j=0; j<l->taille_bloc/2; j++) {
 	  tempG[i+j] = l->source->buffer[i_buffer+j];
 	  tempD[i+j] = l->source->lecteur->silence;
 	}
 	for (j=l->taille_bloc/2; j<l->taille_bloc; j++) {
 	  tempD[i+j] = l->source->buffer[i_buffer+j];
 	  tempG[i+j] = l->source->lecteur->silence;
 	}
 	// mise a jour des indices
 	i += l->taille_bloc;
 	l->position += l->vitesse;
 	if (l->position < 0 || l->position > l->dernier_bloc) {
 	  if (l->repetition == FAUX) l->statut = STATUT_STOP;
 	  if (l->position < 0) l->position = l->dernier_bloc;
 	  else                 l->position = 0;
 	}
   }
   // On mixe la lecture courante :
   SDL_MixAudio(stream, tempG, i,
 		   (int)(l->volume*(1.0-l->balance)*SDL_MIX_MAXVOLUME));
   SDL_MixAudio(stream, tempD, i,
 		   (int)(l->volume*l->balance      *SDL_MIX_MAXVOLUME));
 }

 // Création dynamique d'une lecture
 Lecture * creerLecture(Source * _source, STATUT _statut, LIBERE _liberation, BOOLEAN _repetition, float _volume, float _vitesse, float _balance) {
   Lecture * l = (Lecture *)malloc(sizeof(Lecture));
   l->source     = _source;
   l->statut     = _statut;
   l->liberation = _liberation;
   l->repetition = _repetition;
   l->volume     = _volume;
   l->vitesse    = _vitesse;
   l->balance    = _balance;
   SDL_LockMutex(l->source->lecteur->mutex);
   l->taille_bloc  = l->source->lecteur->taille_bloc;
   l->dernier_bloc = l->source->taille/l->taille_bloc - 1;
   l->position     = l->vitesse<0 ? l->dernier_bloc : 0;
   // On inscrit la lecture dans la source :
   if (l->source->erreur == FAUX) {
 	if (l->source->nbl == NBMAX_LECTURES) return NULL;
 	l->source->lectures[l->source->nbl] = l;
 	l->source->nbl++;
   }
   SDL_UnlockMutex(l->source->lecteur->mutex);
   return l;
 }

 // Démarrage de la lecture
 void lecture(Lecture * l) {
   l->statut = STATUT_PLAY;
 }

 // Suspension de la lecture
 void pause(Lecture * l) {
   l->statut = STATUT_PAUSE;
 }

 // Arrêt de la lecture (suspension + réinitialisation de position)
 void stop(Lecture * l) {
   l->statut = STATUT_STOP;
   if (l->vitesse < 0) l->position = l->dernier_bloc;
   else                l->position = 0;
 }

 // Affichage des caractéristiques de la lecture :
 void printLecture(Lecture * l) {
   float instant_lect = l->position/l->source->lecteur->frequence;
   int minutes = (int)(instant_lect / 60);
   float secondes = instant_lect - (minutes*60);
   int minutesT = (int)(l->source->duree / 60);
   float secondesT = l->source->duree - (minutesT*60);
   printf( "[ %s | %s | %s | vit %.2f | bal %.2f | vol %.2f | %02i'%05.2f / %02i'%05.2f ]",
 	  (l->liberation == LIBERE_ALAFIN ? "temporaire" : (l->liberation == LIBERE_JAMAIS ? "persistent" : "BOUM !")),
 	  (l->statut == STATUT_PLAY ? "->" :
 	   (l->statut == STATUT_STOP ? "__" : "||")),
 	  (l->repetition ? "R" : "1"),
 	  l->vitesse,
 	  l->balance,
 	  l->volume,
 	  minutes,
 	  secondes,
 	  minutesT,
 	  secondesT
 	  );
 }
